package com.sensoro.beacon.kit;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.os.IBinder;
import android.util.Log;

import com.sensoro.beacon.kit.SensoroBeaconConnection.SensoroException;

import java.util.HashMap;

/**
 * This class can manage the sensoro beacon service.The class uses the singleton
 * mode.If you want to get the instance of the class,use
 * {@link com.sensoro.beacon.kit.SensoroBeaconManager#getInstance(Context)}
 */
public class SensoroBeaconManager {
    private static final String TAG = SensoroBeaconManager.class.getSimpleName();
    protected static final boolean DEBUG = true;

    /**
     * SDK version
     */
    public static final String SDK_VERSION = "3.2.2";

    private static final String KEY_MAP = "KEY_MAP";
    private static final String BROADCAST_KEY_SECRET = "password";
    private static final int RAW_KEY_LENGTH = 40;
    private static final int ENCRYPT_KEY_LENGTH = 90;

    // 默认扫描beacon的时间间间隔
    static final long DEFAULT_FOREGROUND_SCAN_PERIOD = 1100; // 默认前台beacon扫描时间
    static final long DEFAULT_FOREGROUND_BETWEEN_SCAN_PERIOD = 0; // 默认前台beacon扫描时间间隔
    static final long DEFAULT_BACKGROUND_SCAN_PERIOD = 1100; // 默认后台beacon扫描时间
    static final long DEFAULT_BACKGROUND_BETWEEN_SCAN_PERIOD = 10 * 1000; // 默认后台beacon扫描时间间隔
    static final long DEFAULT_UPDATE_BEACON_PERIOD = 1000; // beacon定时更新时间间隔

    static volatile long FOREGROUND_SCAN_PERIOD = DEFAULT_FOREGROUND_SCAN_PERIOD;
    static volatile long FOREGROUND_BETWEEN_SCAN_PERIOD = DEFAULT_FOREGROUND_BETWEEN_SCAN_PERIOD;
    static volatile long BACKGROUND_SCAN_PERIOD = DEFAULT_BACKGROUND_SCAN_PERIOD;
    static volatile long BACKGROUND_BETWEEN_SCAN_PERIOD = DEFAULT_BACKGROUND_BETWEEN_SCAN_PERIOD;
    static volatile long UPDATE_BEACON_PERIOD = DEFAULT_UPDATE_BEACON_PERIOD;
    static volatile long OUT_OF_RANGE_DELAY = 8 * 1000; // 如果在该时间间隔内没有扫描到已经发现的beacon，则认为这个beacon已经离开
    static final String BLUETOOTH_IS_NOT_ENABLED = "BluetoothIsNotEnabled";// 异常字符串蓝牙没有开启

    boolean isBeaconProcessServiceBind; // 是否绑定beaconProcessService
    boolean isServiceStrating; // 服务正在启动

    private BeaconManagerListener beaconManagerListener;

    private static SensoroBeaconManager singleInstance;

    private boolean isBleEnabled;// 蓝牙是否开启

    Context context = null;
    BeaconProcessService beaconProcessService = null;

    private HashMap<String,byte[]> broadcastKeyMap;

    /**
	 * Get the instance of SensoroBeaconManager
     *
     * @param context The android context.
     * @return
     */
    public static SensoroBeaconManager getInstance(Context context) {
        if (context == null) {
            return null;
        }
        if (singleInstance == null) {
            singleInstance = new SensoroBeaconManager(context);
        }
        return singleInstance;
    }

    /**
     * Add a broadcast key to decrypt a Yunzi with encryption. </br>
     * If you have more keys, you can call this method more than once.
     *
     * @param broadcastKey
     */
    public boolean addBroadcastKey(String broadcastKey){
        /**
         * 广播 KEY 分为两个版本：带有效期和不带有效期的。
         * 第一个版本的 KEY，可以直接设置到 iBeacon 中；无需校验.
         * 第二个版本的 KEY，需要判断有效期，只有在有效期内的 KEY 才允许设置到 iBeacon 中。
         *
         * 第二个版本 KEY 格式：01(版本号：2个字符) + 加密数据(48个字符，包含原始 KEY 和时间戳)。
         * 解密后格式：01(版本号：2个字符) + 原始 KEY(第一个版本 KEY: 20个字符) +  时间戳(KEY 过期时间：8个字符)
         */
        if (broadcastKey == null) {
            return false;
        }
        int keyLength = broadcastKey.length();
        if (RAW_KEY_LENGTH == keyLength) {
            addKey(broadcastKey);
            return true;
        } else if (ENCRYPT_KEY_LENGTH == keyLength) {
            String decrypt = SensoroUtils.decrypt_AES_256(broadcastKey.substring(2, keyLength), BROADCAST_KEY_SECRET);
            if (decrypt == null) {
                return false;
            } else {
                long expiryDate = Long.valueOf(Integer.parseInt(decrypt.substring(40, decrypt.length()), 16));
                long currentDate = System.currentTimeMillis();
                if (currentDate > expiryDate * 1000) {
                    return false;
                } else {
                    addKey(decrypt.substring(0,40));
                    return true;
                }
            }
        } else {
            return false;
        }
    }

    private void addKey(String broadcastKey){
        synchronized (broadcastKeyMap) {
            String secret = broadcastKey.substring(0,28);
            byte[] secretBytes = hexToByte(secret);
            String keyId = broadcastKey.substring(28,32);
            if (broadcastKeyMap!= null){
                broadcastKeyMap.put(keyId,secretBytes);
            }
            if (beaconProcessService != null) {
                beaconProcessService.setBroadcastKeyMap(broadcastKeyMap);
            }
        }
    }
    
    private static final char[] HEX_CHAR_TABLE = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
	private static final byte[] HEX_TABLE = { 0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF };
	private byte[] hexToByte(String hexString) {
		if (hexString == null || hexString.length() == 0)
			return null;
		if (hexString.length() % 2 != 0)
			throw new RuntimeException();
		byte[] data = new byte[hexString.length() / 2];
		char[] chars = hexString.toCharArray();
		for (int i = 0; i < hexString.length(); i = i + 2) {
			data[i / 2] = (byte) (HEX_TABLE[getHexCharValue(chars[i])] << 4 | HEX_TABLE[getHexCharValue(chars[i + 1])]);
		}
		return data;
	}
	private int getHexCharValue(char c) {
		int index = 0;
		for (char c1 : HEX_CHAR_TABLE) {
			if (c == c1) {
				return index;
			}
			index++;
		}
		return 0;
	}

    private SensoroBeaconManager(Context context) {
        this.context = context;
        broadcastKeyMap = new HashMap<String,byte[]>();
    }

    /**
     * Set a BeaconManager listener
     *
     * @param beaconManagerListener The listener to set.
     */
    public void setBeaconManagerListener(BeaconManagerListener beaconManagerListener) {
        this.beaconManagerListener = beaconManagerListener;
        if (beaconProcessService != null) {
            beaconProcessService.registerListener(beaconManagerListener);
        }
    }

    protected BeaconManagerListener getBeaconManagerListener() {
        return beaconManagerListener;
    }

    boolean isBound() {
        if (beaconProcessService != null) {
            return beaconProcessService.isBound();
        } else {
            return false;
        }
    }

    void setBackgroundMode(boolean backgroundMode) {
        if (beaconProcessService != null) {
            beaconProcessService.setBackgroundMode(backgroundMode);
        }
    }

    protected void startService(Intent intent) throws Exception {
        if (!isBleEnabled) {
            throw new Exception(BLUETOOTH_IS_NOT_ENABLED);// 抛出蓝牙关闭异常
        }
        if (context != null) {
            context.startService(intent);
        }
    }

    /**
     * Start Sensoro iBeacon service.
     *
     * @throws SensoroException
     */
    public void startService() throws Exception {
        if (DEBUG) {
            Log.d(TAG, "startService");
        }

        if (isServiceStrating) {
            return;
        }

        isBleEnabled = isBluetoothEnabled();// 获取当前蓝牙状态

        if (!isBleEnabled) {
            throw new Exception(BLUETOOTH_IS_NOT_ENABLED);// 抛出蓝牙关闭异常
        }

        if (!isBeaconProcessServiceBind) {
            isServiceStrating = true;

            Intent intent = new Intent();
            intent.setClass(context, BeaconProcessService.class);
            intent.putExtra(KEY_MAP,broadcastKeyMap);
            if (DEBUG) {
                Log.d(TAG, "go to bind service");
            }
            context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
        } else {
            // Log.v(TAG, "BeaconProcessService has been bind");
        }

        // 启动 SensoroBeaconService
        // Intent intent = new Intent();
        // intent.setClass(context,SensoroBeaconService.class);
        // context.startService(intent);
    }

    /**
     * Stop Sensoro iBeacon service.
     */
    public void stopService() {
        if (DEBUG) {
            Log.d(TAG, "stopService");
        }
        if (isBeaconProcessServiceBind) {
            context.unbindService(serviceConnection);
            isBeaconProcessServiceBind = false;
        }
    }

    private ServiceConnection serviceConnection = new ServiceConnection() {

        @Override
        public void onServiceDisconnected(ComponentName name) {
            isServiceStrating = false;

            isBeaconProcessServiceBind = false;
            // Log.i(TAG, "onServiceDisconnected");
            if (beaconProcessService != null) {
                beaconProcessService.unregisterListener(beaconManagerListener);
            }
        }

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            isServiceStrating = false;

            BeaconProcessService.BeaconProcessServiceBinder binder = (BeaconProcessService.BeaconProcessServiceBinder) service;
            beaconProcessService = binder.getService();
            if (beaconProcessService != null) {
//                beaconProcessService.registerListener(beaconManagerListener);
                beaconProcessService.setBeaconExitTime(OUT_OF_RANGE_DELAY);
                beaconProcessService.setBroadcastKeyMap(broadcastKeyMap);
            }
            isBeaconProcessServiceBind = true;
        }
    };

    /**
     * Set the callback period (in milliseconds) of
     * {@link com.sensoro.beacon.kit.SensoroBeaconService#onUpdateBeacon}
     *
     * @param periodMills Default value is 1 s(unit:ms)
     */
    protected void setUpdateBeaconPeriod(long periodMills) {
        UPDATE_BEACON_PERIOD = periodMills;
    }

    /**
     * Set iBeacon scan period (in milliseconds) in foreground
     *
     * @param periodMills Default value is 1.1 s(unit:ms)
     * @hide
     */
    protected void setForegroundScanPeriod(long periodMills) {
        FOREGROUND_SCAN_PERIOD = periodMills;
    }

    /**
     * Set the period (in milliseconds) between two scan periods in foreground
     *
     * @param periodMills Default value is 0 s(unit:ms)
     */
    protected void setForegroundBetweenScanPeriod(long periodMills) {
        FOREGROUND_BETWEEN_SCAN_PERIOD = periodMills;
    }

    /**
     * set iBeaon scan period (in milliseconds) in background
     *
     * @param periodMills Default value is 10 s(unit:ms)
     */
    protected void setBackgroundScanPeriod(long periodMills) {
        BACKGROUND_SCAN_PERIOD = periodMills;
    }

    /**
     * Set the period (in milliseconds) between two scan periods in background
     *
     * @param periodMills Default value is 5 min(unit:ms)
     */
    protected void setBackgroundBetweenScanPeriod(long periodMills) {
        BACKGROUND_BETWEEN_SCAN_PERIOD = periodMills;
    }

    /**
     * Set the delay (in milliseconds) of disappearence of the beacon.If the
     * beacon can not be found in this time,{@link BeaconManagerListener#onGoneBeacon(Beacon)} will be called.
     *
     * @param delayMills The delay (in milliseconds)of the disappearence of the beacon.The
     *                   default value is 8 seconds.
     */
    private void setOutOfRangeDelay(long delayMills) {
        OUT_OF_RANGE_DELAY = delayMills;
    }

    /**
     * Check whether the bluetooth is enabled.
     *
     * @return
     */
    public boolean isBluetoothEnabled(){
        if (context != null){
            BluetoothManager bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
            BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();
            if (bluetoothAdapter.isEnabled()) {// 蓝牙开启
                return true;
            } else {// 蓝牙关闭
                return false;
            }
        } else {
            return false;
        }
    }

    /**
     * Check whether the BLE is supported.
     *
     * @return
     */
    public boolean isBLESuppotred(){
        // Use this check to determine whether BLE is supported on the device. Then
        // you can selectively disable BLE-related features.
        if (context != null){
            if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
                return false;
            } else {
                return true;
            }
        } else {
            return false;
        }
    }
}
